# 1.回顾
冷启动 Activity,目标 App 启动后,会去执行 LaunchActivityItem 和 ResumeActivityItem 这 2 个事务,整体的调用链如下:
LaunchActivityItem::execute
ActivityThread::handleLaunchActivity
ActivityThread::performLaunchActivity
Instrumentation::newActivity -- 构建 Activity
Activity::attach -- 构建 Window
Window::init
Window::setWindowManager
Instrumentation::callActivityOnCreate
Activity::performCreate
Activity::onCreate
Activity::setContentView -- 构建 DecorView
ResumeActivityItem::execute
ActivityThread::handleResumeActivity
ActivityThread::performResumeActivity
Activity::performResume
Instrumentation::callActivityOnResume
Activity::onResume
WindowManagerImpl::addView -- 构建 ViewRootImpl
WindowManagerGlobal::addView
ViewRootImpl::setView -- 显示 Window
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
setView 是显示 Activity 的起点,是我们关注的重点,其主要调用链如下:
ViewRootImpl::setView
ViewRootImpl::requestLayout
ViewRootImpl::scheduleTraversals
ViewRootImpl.TraversalRunnable::run -- 异步操作
ViewRootImpl::doTraversal
ViewRootImpl::performTraversals
ViewRootImpl::measureHierarchy -- 第 3 步预 measure View 树
ViewRootImpl::relayoutWindow -- 第 4 步:relayoutWindow,添加 SurfaceControl/Layer,测量窗口大小,初始化 BBQ
Session::relayout -- 远程调用,构建 SurfaceControl,测量窗口大小,Transaction 配置 Layer
ViewRootImpl::updateBlastSurfaceIfNeeded -- 初始化 BBQ
ViewRootImpl::performMeasure -- 第 5 步:View 的 测量布局绘制
ViewRootImpl::performLayout
ViewRootImpl::performDraw
ViewRootImpl::createSyncIfNeeded -- 第 6 步:通知 WMS,客户端已经完成绘制,可以显示 Surface 了
Session.addToDisplayAsUser --- 第 1 步:addWindow
mWindowLayout.computeFrames --- 第 2 步:预计算 Window 尺寸
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
整体上我们可以把 Activity 的显示过程划分为 5 个阶段:
- 阶段一:Activity Window DecorView 的初始化
- 阶段二:添加 Window,预测量 Window 尺寸
- 阶段三:预测量View 树,添加 SurfaceControl/Layer,测量 Window 大小,初始化 BLASTBufferQueue
- 阶段四:View 树的测量布局绘制
- 阶段五:显示窗口
上一节分析了阶段一,本节分析阶段二。
# 2.ViewRootImpl::setView 方法整体分析
接下来我们分析 RootViewImpl 的 setView 方法的整体流程:
// /frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
setView(view, attrs, panelParentView, UserHandle.myUserId());
}
2
3
4
接着调用另一个重载:
// /frameworks/base/core/java/android/view/ViewRootImpl.java
// 与 WMS 通信的 binder 代理端
final IWindowSession mWindowSession;
// 对应的 DecorView
View mView;
// Binder 服务端类
final W mWindow;
public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
// 窗口的大小
final Rect mWinFrame
//
private final ClientWindowFrames mTmpFrames = new ClientWindowFrames();
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
// 就是 DecorView
mView = view;
// ......
// 将布局参数保存到成员变量 mWindowAttributes 中
mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
// 使用 blast 标志位
mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
attrs = mWindowAttributes;
// ......
// 关注点3,布局,异步的
requestLayout();
// ......
try {
// ......
Rect attachedFrame = new Rect();
final float[] compatScale = { 1f };
// 关注点1,远程调用到 wms,添加 window
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, compatScale);
// ......
} catch (RemoteException | RuntimeException e) {
// ......
} finally {
if (restore) {
attrs.restore();
}
}
// ......
final WindowConfiguration winConfig = getCompatWindowConfiguration();
// 关注点2,预计算 Window 的尺寸
// 计算的结果存放在 mTmpframes 中
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
// 设置到全部变量 mWinFrame 中
setFrame(mTmpFrames.frame, true /* withinRelayout */);
// .....
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
主要关注与 Window 显示相关的三点:
- 关注点1,添加 window
- 关注点2,预计算 window 尺寸
- 关注点3,requestLayout,这个过程是异步的,虽然代码在前面,实际会后执行
本节主要分析1,2 点。
# 3. Window 添加过程分析
本小节主要分析上一节的关注点 1,添加 window 的过程。
在 ViewRootImpl::setView
中会调用 WindowSession.addToDisplayAsUser
来添加窗口
// 关注点1,远程到 wms,添加 window
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, compatScale);
2
3
4
5
这部分主要的任务是在 WMS 中创建当前 Activity/窗口 对应的 WindowState 对象,并挂载到窗口容器树中去。
addToDisplayAsUser 是一个远程调用,调用到 SystemServer 这边的 Session 匿名 Binder 服务:
// /frameworks/base/services/core/java/com/android/server/wm/Session.java
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}
2
3
4
5
6
7
8
9
10
11
接着调用到 WindowManagerService 中的 addWindow 方法:
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
// 所有的 WindowState 都会保存在这里
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
outActiveControls.set(null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
// 检查权限
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
appOp);
if (res != ADD_OKAY) {
return res;
}
// 父窗口,应用 Activity 逻辑是没有父窗口的。
WindowState parentWindow = null;
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
final int type = attrs.type;
synchronized (mGlobalLock) {
// ......
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
// ......
// 窗口已经添加,直接 return
if (mWindowMap.containsKey(client.asBinder())) {
ProtoLog.w(WM_ERROR, "Window %s is already added", client);
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
// ......
ActivityRecord activity = null;
final boolean hasParent = parentWindow != null;
// 通过 attrs.token 找到对应的 WindowToken/ActivityRecord,后续 new WindowState 要用
// 如果是 Activity 窗口,token 的类型就是 ActivityRecord,在 Activity 启动的过程中,已经赋值,值不为 null
// 如果是 系统窗口或者悬浮窗,token 的类型就是 WindowToken,值为 null,还没有创建
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
boolean addToastWindowRequiresToken = false;
final IBinder windowContextToken = attrs.mWindowContextToken;
// .....
// 关注点 1
// 创建当前窗口对应的 WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
ProtoLog.w(WM_ERROR, "Adding window client %s"
+ " that is dead, aborting.", client.asBinder());
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (win.getDisplayContent() == null) {
ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
// 调整 window 的参数
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
callingPid);
win.setRequestedVisibleTypes(requestedVisibleTypes);
// 验证 Window 是否可以添加,主要是验证权限
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
return res;
}
// ......
// WindowState 缓存到 mWindowMap 中
win.attach();
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
// .....
// 关注点2
// WindowState 挂载到窗口容器树中
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);
displayPolicy.setDropInputModePolicy(win, win.mAttrs);
// .....
// .....
return res;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
主要的流程,找到 ActivityRecord,构建一个新的 WindowState,挂载到窗口容器树中。还有匿名服务 Client 与 WindowState 的映射关系会被保存到 mWindowMap 中,方便后续找到 WindowState。
# 4. 预计算 window 尺寸
关注点2,调用 computeFrames,预计算 Window 的尺寸:
// /frameworks/base/core/java/android/view/ViewRootImpl.java
// 关注点2,预计算 Window 的尺寸,传入 UNSPECIFIED_LENGTH,不限制尺寸,实际的限制就是屏幕尺寸
// 计算的结果存放在 mTmpframes 中
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
// 计算结果保存到变量 mWinFrame 中
setFrame(mTmpFrames.frame, true /* withinRelayout */);
2
3
4
5
6
7
8
9
10
11
这里会通过不指定尺寸 UNSPECIFIED_LENGTH,去预计算一个 Window 的尺寸信息。预计算的主要目的是为后续的预 measure 服务,让预 measure 有一个参考尺寸。
computeFrames 的具体实现如下:
// /frameworks/base/core/java/android/view/WindowLayout.java
// # WindowLayout
//参数:
// attrs: 窗口参数
// state: Insets状态
// displayCutoutSafe: 剪裁区域
// windowBounds: 窗口的边界,来自配置,对于 Activity 就是屏幕大小
// windowingMode: 窗口模式
// requestedWidth,requestedHeight: 请求的宽高
// requestedVisibleTypes: 请求显示的内边距类型
// compatScale:兼容性缩放因子
// frames : 输出的窗口位置信息
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
float compatScale, ClientWindowFrames frames) {
// 获取窗口的类型、标志和私有标志,以便后面的计算使用
final int type = attrs.type;
final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
// FLAG_LAYOUT_IN_SCREEN: Window flag for attached windows: Place the window within the entire screen, ignoring
// any constraints from the parent window.
// 判断窗口是否指定了 FLAG_LAYOUT_IN_SCREEN 标志,该标志表示窗口是否应该扩展到屏幕的尺寸
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
// 获取输出的各个位置信息,包括显示区域、父窗口区域和窗口区域
final Rect attachedWindowFrame = frames.attachedFrame;
final Rect outDisplayFrame = frames.displayFrame;
final Rect outParentFrame = frames.parentFrame;
final Rect outFrame = frames.frame;
// 通过 InsetsState 计算窗口的 Insets 值。
// 这里实际是去所有 Insets 的最大值
// Compute bounds restricted by insets
final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
attrs.isFitInsetsIgnoringVisibility());
// 获取窗口在各个方向上是否需要计算 Insets 值,然后根据这些方向计算出对应方向的 Insets 值
final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
final int left = (sides & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
final int top = (sides & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
final int right = (sides & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
// 将显示区域的边界计算出来,它是应用窗口在整个屏幕上的可见区域。其中,
// windowBounds 表示应用窗口的位置和大小,left、top、right 和 bottom 表示计算得到的窗口与屏幕边界的间距。
// outDisplayFrame 中保存了 Window 可以显示的最大区域,在屏幕区域的基础上剔除了 Inset 的区域。
outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
windowBounds.right - right, windowBounds.bottom - bottom);
// 计算出应用窗口父容器的边界
if (attachedWindowFrame == null) {
// 如果应用窗口未附加到其他窗口上,则父容器边界与显示区域相同。
outParentFrame.set(outDisplayFrame);
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
final InsetsSource source = state.peekSource(ID_IME);
if (source != null) {
outParentFrame.inset(source.calculateInsets(
outParentFrame, false /* ignoreVisibility */));
}
}
} else {
// 如果应用窗口附加到其他窗口上,则父容器边界要根据应用窗口所附加的窗口的位置和大小进行调整
outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
}
// 刘海处理
// Compute bounds restricted by display cutout
final int cutoutMode = attrs.layoutInDisplayCutoutMode;
// cutout表示屏幕凸起部分的信息
final DisplayCutout cutout = state.getDisplayCutout();
// displayCutoutSafe 表示在应用窗口可见区域中,不会被刘海遮挡的区域
final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
frames.isParentFrameClippedByDisplayCutout = false;
// 处理窗口与显示屏中的刘海屏幕(也称为"切口")之间的关系,以确保窗口不会被刘海屏幕覆盖
// 在Android系统中,刘海屏幕的信息由DisplayCutout类来表示,而这个类的实例通常可以从InsetsState对象中获取
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
// the cutout safe zone.
final Rect displayFrame = state.getDisplayFrame();
// cutoutMode 默认值为 LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
if (displayFrame.width() < displayFrame.height()) {
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
} else {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
displayCutoutSafeExceptMaybeBars.right = MAX_X;
}
}
final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
if (layoutInScreen && layoutInsetDecor
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
displayFrame, systemBars(), requestedVisibleTypes);
if (systemBarsInsets.left > 0) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
if (systemBarsInsets.top > 0) {
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
}
if (systemBarsInsets.right > 0) {
displayCutoutSafeExceptMaybeBars.right = MAX_X;
}
if (systemBarsInsets.bottom > 0) {
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
// 刘海屏处理结束
// 处理输入法类型窗口
if (type == TYPE_INPUT_METHOD
&& displayCutoutSafeExceptMaybeBars.bottom != MAX_Y
&& state.calculateInsets(displayFrame, navigationBars(), true).bottom > 0) {
// 这是为了确保输入法窗口不会被底部切口遮挡,同时又能够利用底部空间。
// The IME can always extend under the bottom cutout if the navbar is there.
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
// 用 attachedWindowFrame 和 layoutInScreen 两个变量判断窗口是否连接到了父级并且没有被放置在屏幕之外
// 即窗口在父级内进行了布局
// 如果两个条件都满足,说明窗口没有超出父级边界,不需要被剪裁,因此不需要处理刘海屏幕问题
final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
// TYPE_BASE_APPLICATION windows are never considered floating here because they don't
// get cropped / shifted to the displayFrame in WindowState.
// floatingInScreenWindow 变量判断窗口是否为非全屏窗口,同时被放置在屏幕之内,且窗口类型不是 TYPE_BASE_APPLICATION
// 如果窗口类型不是 TYPE_BASE_APPLICATION,则可以认为该窗口是浮动窗口,需要考虑刘海屏幕问题。如果是全屏窗口,则不需要考虑刘海屏幕问题。
final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
&& type != TYPE_BASE_APPLICATION;
// Windows that are attached to a parent and laid out in said parent already avoid
// the cutout according to that parent and don't need to be further constrained.
// Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
// They will later be cropped or shifted using the displayFrame in WindowState,
// which prevents overlap with the DisplayCutout.
// 如果窗口不连接到父级,且不是浮动窗口,则可以认为窗口会被刘海屏遮挡
// 做裁剪处理
if (!attachedInParent && !floatingInScreenWindow) {
mTempRect.set(outParentFrame);
outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
}
outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}
// noLimits 表示是否启用无限制的布局
final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// inMultiWindowMode 表示窗口是否处于多窗口模式
final boolean inMultiWindowMode = WindowConfiguration.inMultiWindowMode(windowingMode);
// 启用了无限制的布局、窗口不是系统错误类型(TYPE_SYSTEM_ERROR)
// 并且窗口不处于多窗口模式,那么将窗口的显示范围设置为整个屏幕
if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
outDisplayFrame.left = MIN_X;
outDisplayFrame.top = MIN_Y;
outDisplayFrame.right = MAX_X;
outDisplayFrame.bottom = MAX_Y;
}
// compatScale 表示兼容比例
final boolean hasCompatScale = compatScale != 1f;
// 父级窗口的宽度和高度
final int pw = outParentFrame.width();
final int ph = outParentFrame.height();
// 表示窗口是否扩展到刘海区
final boolean extendedByCutout =
(attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
// 请求的窗口宽度和高度等
int rw = requestedWidth;
int rh = requestedHeight;
// x,y,w,h 后面将用于计算窗口的位置和大小
float x, y;
int w, h;
// 处理窗口的宽度和高度
if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
// 窗口的宽度和高度是UNSPECIFIED_LENGTH,且扩展到刘海区就重新计算高度
// 计算结果为如果窗口的宽度 attrs.width 大于等于0,就使用 attrs.width 作为窗口的宽度,
// 否则使用父容器的宽度 pw 作为窗口的宽度
rw = attrs.width >= 0 ? attrs.width : pw;
}
if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
rh = attrs.height >= 0 ? attrs.height : ph;
}
// 根据窗口的属性计算窗口的宽度和高度
if ((attrs.flags & FLAG_SCALED) != 0) {
if (attrs.width < 0) {
w = pw;
} else if (hasCompatScale) {
w = (int) (attrs.width * compatScale + .5f);
} else {
w = attrs.width;
}
if (attrs.height < 0) {
h = ph;
} else if (hasCompatScale) {
h = (int) (attrs.height * compatScale + .5f);
} else {
h = attrs.height;
}
} else {
if (attrs.width == MATCH_PARENT) {
w = pw;
} else if (hasCompatScale) {
w = (int) (rw * compatScale + .5f);
} else {
w = rw;
}
if (attrs.height == MATCH_PARENT) {
h = ph;
} else if (hasCompatScale) {
h = (int) (rh * compatScale + .5f);
} else {
h = rh;
}
}
// 处理是否需要缩放
if (hasCompatScale) {
x = attrs.x * compatScale;
y = attrs.y * compatScale;
} else {
x = attrs.x;
y = attrs.y;
}
// 如果窗口处于多窗口模式且不是全屏任务
// PRIVATE_FLAG_XX标志位表示窗口是否应该布局在其父窗口的框架内,而不是在父窗口的内容内
// 在多窗口模式下,如果子窗口不在父窗口的框架内,则需要确保它适合于父窗口的大小,因为父窗口可能是非全屏的任务
// 所以如果满足条件则需要调整子窗口的大小以适应父窗口的大小。
if (inMultiWindowMode
&& (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) == 0) {
// Make sure window fits in parent frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
w = Math.min(w, pw);
h = Math.min(h, ph);
}
// 如果不在多窗口模式下,或者不是TYPE_BASE_APPLICATION类型,且noLimits属性为false,
// 则需要将窗口大小限制在显示器内。fitToDisplay变量用于表示是否需要将窗口大小限制在显示器内
final boolean fitToDisplay = !inMultiWindowMode
|| ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
// 计算出窗口在父视图中的矩形框的位置和大小,并将结果保存在outFrame变量中
Gravity.apply(attrs.gravity, w, h, outParentFrame,
(int) (x + attrs.horizontalMargin * pw),
(int) (y + attrs.verticalMargin * ph), outFrame);
// 计算出窗口在整个屏幕范围内的位置和大小,并将结果保存在outFrame变量中
if (fitToDisplay) {
Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
}
if (extendedByCutout) {
extendFrameByCutout(displayCutoutSafe, outDisplayFrame, outFrame,
mTempRect);
}
if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle()
+ " frames=" + frames
+ " windowBounds=" + windowBounds.toShortString()
+ " requestedWidth=" + requestedWidth
+ " requestedHeight=" + requestedHeight
+ " compatScale=" + compatScale
+ " windowingMode=" + WindowConfiguration.windowingModeToString(windowingMode)
+ " displayCutoutSafe=" + displayCutoutSafe
+ " attrs=" + attrs
+ " state=" + state
+ " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
没有太多计算,主要是一些判断。最终的结果主要取决于 LayoutParams 参数。
计算结果保存在最后一个参数 ClientWindowFrames frames
中。
public class ClientWindowFrames implements Parcelable {
/** The actual window bounds. */
public final @NonNull Rect frame = new Rect();
/**
* The container frame that is usually the same as display size. It may exclude the area of
* insets if the window layout parameter has specified fit-insets-sides.
*/
public final @NonNull Rect displayFrame = new Rect();
/**
* The frame to be referenced while applying gravity and MATCH_PARENT.
*/
public final @NonNull Rect parentFrame = new Rect();
/**
* The frame this window attaches to. If this is not null, this is the frame of the parent
* window.
*/
public @Nullable Rect attachedFrame;
// ......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
接着调用 setFrame:
// /frameworks/base/core/java/android/view/ViewRootImpl.java
final Rect mWinFrame;
// withinRelayoutL:true
private void setFrame(Rect frame, boolean withinRelayout) {
// 主要是 mWinFrame
mWinFrame.set(frame);
if (withinRelayout) {
mLastLayoutFrame.set(frame);
}
final WindowConfiguration winConfig = getCompatWindowConfiguration();
mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
? winConfig.getMaxBounds()
: frame);
// Surface position is now inherited from parent, and BackdropFrameRenderer uses backdrop
// frame to position content. Thus, we just keep the size of backdrop frame, and remove the
// offset to avoid double offset from display origin.
mPendingBackDropFrame.offsetTo(0, 0);
mInsetsController.onFrameChanged(mOverrideInsetsFrame != null ?
mOverrideInsetsFrame : frame);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这里给 mWinFrame,mLastLayoutFrame,mPendingBackDropFrame 几个成员变量赋值,我们主要关注 mWinFrame 这个成员,保存了当前窗口的尺寸。
接着调用了 InsetsController.onFrameChanged 回调,通知 listener,窗口尺寸计算好了。
# 思考
思考一个问题,为什么要预测量?
预测量给的参数是 UNSPECIFIED_LENGTH,也就是不限制 Window 的尺寸。剩下的限制就是屏幕的大小。实际就是以屏幕大小去测量 Window 大小。在屏幕大小的基础上根据 App 的配置信息剔除系统窗口的影响(Inset 刘海 输入法等),剩下的就是 Window 可以显示的区域。
测量出的值主要是给后续的预 Measure 提供一个基准参考值。